#include <vcl.h>
#pragma hdrstop

#pragma package(smart_init)
#include "uMigrations.h"
#include "IniFiles.hpp"

#define MODULE_TAG 4



AnsiString CMigrations::m_FichierCommun,CMigrations::m_FichierDossier;



/*
  Code : code de l'assistant. Ex : "SI".
  Dossier : true si on migre le dossier ou false si on migre le commun. On migre à 95% le dossier.
*/
__fastcall CMigrations::CMigrations(const AnsiString &Code,bool Dossier,const AnsiString &Section,bool VersionLisible)
{
  ASSERT(!Code.IsEmpty());

  m_PlanterSiVersionSuperieure = true;
  m_Code = Code;
  m_Section = Section;
  m_Dossier = Dossier;
  m_VersionLisible = VersionLisible;
  m_ListeMigrations = new TStringList();
  RAZ();
}



__fastcall CMigrations::~CMigrations()
{
  RAZ();
  delete m_ListeMigrations;
}



void __fastcall CMigrations::RAZ()
{
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    delete (InfoMigration *)m_ListeMigrations->Objects[i];
  m_ListeMigrations->Clear();
}



/*
  Lance les migrations si besoin.
*/
bool __fastcall CMigrations::Migrer(const AnsiString &Titre)
{
  unsigned int Version,VersionPrecedente,VersionMax,IndiceDerniereMigrationALancer;
  bool Lancer,LancerSiAutreMigration,AuMoinsUneMigration = false;
  CBarre Barre;
  Barre.Titre = Titre;
  VersionMax = VersionPrecedente = IndiceDerniereMigrationALancer = 0;
  Version = LireVersion();
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    {
      if (((InfoMigration *)m_ListeMigrations->Objects[i])->Version > VersionMax)
        VersionMax = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
      Lancer = Version < ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
      LancerSiAutreMigration = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version == 0;
      ((InfoMigration *)m_ListeMigrations->Objects[i])->Migrer = Lancer || LancerSiAutreMigration;
      if (Lancer || LancerSiAutreMigration)
        {
          if (Lancer)
            AuMoinsUneMigration = true;
          if (!Barre.Messages.IsEmpty())
            Barre.Messages = Barre.Messages + "\n";
          Barre.Messages = Barre.Messages + m_ListeMigrations->Strings[i];
          IndiceDerniereMigrationALancer = i;
        }
    }
  if (Version > VersionMax)
    if (m_PlanterSiVersionSuperieure)
      {//Le dossier est supérieur à la version max
        if (m_Dossier)
          throw EUser(TAGL,"Erreur durant la migration.\n"
                           "Le dossier a été traité avec une version postérieure.\n"
                           "  Version dans le dossier : " + ChaineVersion(Version) + "\n"
                           "  Version de l'assistant : " + ChaineVersion(VersionMax));
        else
          throw EUser(TAGL,"Erreur durant la migration.\n"
                           "Le commun est dans une version postérieure.\n"
                           "  Version du commun : " + ChaineVersion(Version) + "\n"
                           "  Version du logiciel : " + ChaineVersion(VersionMax));
      }
    else
      return false;
  if (!AuMoinsUneMigration)
    return false;                 //Aucune migration à faire
  Barre.Open();
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    {
      try
        {
          if (((InfoMigration *)m_ListeMigrations->Objects[i])->Migrer)
            {
              ((InfoMigration *)m_ListeMigrations->Objects[i])->Fonction(&Barre);
              #ifdef _DEBUG
                ((InfoMigration *)m_ListeMigrations->Objects[i])->Fonction(&Barre);  //On lance une seconde migration identique pour forcer le programmeur à écrire des migrations stables et robustes
              #endif
              if (i != m_ListeMigrations->Count - 1 && IndiceDerniereMigrationALancer > (unsigned int)i)
                Barre.NextMessage();
              if (((InfoMigration *)m_ListeMigrations->Objects[i])->Version != 0)
                {
                  VersionPrecedente = Version;
                  Version = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
                  EcrireVersion(Version);
                }
            }
        }
      catch (Exception &e)
        {
          if ((((InfoMigration *)m_ListeMigrations->Objects[i])->Version == 0) && (VersionPrecedente != 0)) //C'est une migration 0 qui a planté...
            EcrireVersion(VersionPrecedente);                                                               //...on remet l'ancienne version pour relancer la migration
          throw EUser(TAGL,AnsiString("Erreur durant la migration ") + ChaineVersion(((InfoMigration *)m_ListeMigrations->Objects[i])->Version) + "\n" +
                                      "(" + m_ListeMigrations->Strings[i] + ")\n"
                                      "\n" +
                                      e.Message);
        }
    }
  return true;
}



/*
  Ajoute une nouvelle migration.
  Les migrations doivent être ajoutées dans l'ordre croissant des versions.
  Une fonction de migration prent en paramètre un CBarre*.
                            doit pouvoir être appellée une 2ème fois si la première a planté.
*/
void __fastcall CMigrations::AjouterMigration(FonctionMigration Fonction,unsigned int Version,const AnsiString &Texte/*,bool MigrerSiVersionDifferente*/)
{
  #ifdef _DEBUG
    unsigned int VersionMax;
    if (Version != 0)
      for (int i = 0;i < m_ListeMigrations->Count;i++)
        {
          VersionMax = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
          if (Version < VersionMax)
            ASSERT(false);     //Il faut ajouter les migrations dans l'ordre croissant des versions.
        }
  #endif
  InfoMigration *Migration = new InfoMigration();
  Migration->Fonction = Fonction;
  Migration->Version = Version;
  Migration->Migrer = false;
  m_ListeMigrations->AddObject(Texte,(TObject *)Migration);
}



/*
  Fonction d'initialisation pour préciser les chemins des fichiers du commun et du dossier où lire les versions.
  Cette fonction est appellée par le Menufed.
*/
void __fastcall CMigrations::Init(const AnsiString &FichierCommun,const AnsiString &FichierDossier)
{
  m_FichierCommun = FichierCommun;
  m_FichierDossier = FichierDossier;
}



void __fastcall CMigrations::GetInit(AnsiString &FichierCommun,AnsiString &FichierDossier)
{
  FichierCommun = m_FichierCommun;
  FichierDossier = m_FichierDossier;
}



void __fastcall CMigrations::EcrireVersion(unsigned int Version) const
{
  int tag;
  TIniFile *Fichier = NULL;
  AnsiString NomFichier = m_Dossier ? m_FichierDossier : m_FichierCommun;

  ASSERT(!NomFichier.IsEmpty());

  try
    {
      tag = TAGL;
      Fichier = new TIniFile(NomFichier);
      tag = TAGL;
      if (m_VersionLisible)
        Fichier->WriteString(m_Section,m_Code,ChaineVersion(Version));
      else
        Fichier->WriteInteger(m_Section,m_Code,Version);
      #ifdef _DEBUG
        tag = TAGL;
        if (!m_DateDEBUG.IsEmpty())
          Fichier->WriteString(m_Section,m_Code + "_DEBUG",m_DateDEBUG);
      #endif
      tag = TAGL;
      Fichier->UpdateFile();
    }
  catch (Exception &e)
    {
      throw EUser(tag,e);
    }
  delete Fichier;
}



 unsigned int __fastcall CMigrations::LireVersion() const
{
  TIniFile *Fichier;
  int Version;
  AnsiString NomFichier;
  if (m_Dossier)
    NomFichier = m_FichierDossier;
  else
    NomFichier = m_FichierCommun;

  ASSERT(!NomFichier.IsEmpty());

  Fichier = new TIniFile(NomFichier);
  if (m_VersionLisible)
    {
      int i = 1;
      char cLettre = '\0';
      AnsiString StrVersion,StrNum1,StrNum2;
      Version = 0;
      StrVersion = Fichier->ReadString(m_Section,m_Code,EmptyStr);
      if (!StrVersion.IsEmpty())
        {
          while ((i <= StrVersion.Length()) && (StrVersion[i] != '.'))
            {
              StrNum1 += StrVersion[i];
              i++;
            }
          if (i != StrVersion.Length())
            i++;
          while ((i <= StrVersion.Length()) && (StrVersion[i] <= '9'))
            {
              StrNum2 += StrVersion[i];
              i++;
            }
          if (i <= StrVersion.Length())
            cLettre = StrVersion[i];
          try
            {
              if (!StrNum1.IsEmpty())
                Version = StrNum1.ToInt() << 16;
              if (!StrNum2.IsEmpty())
                Version += StrNum2.ToInt() << 8;
              if (cLettre)
                Version += cLettre - 'a' + 1;
            }
          catch (...)
            {
            }
        }
    }
  else
    Version = Fichier->ReadInteger(m_Section,m_Code,0);
  #ifdef _DEBUG
    if (!m_DateDEBUG.IsEmpty())
      {
        ASSERT(m_VersionDEBUG);

        if ((Fichier->ReadString(m_Section,m_Code + "_DEBUG",EmptyStr) != m_DateDEBUG) && ((unsigned int)Version > m_VersionDEBUG))
          Version = m_VersionDEBUG;
      }
  #endif
  delete Fichier;
  return Version;
}



/*
  Fonction à apeller lors de la création des tables quand on rentre la première fois dans l'assistant.
*/
void __fastcall CMigrations::Creer()
{
  unsigned int Version,VersionMax = 0;
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    {
      Version = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
      if (Version > VersionMax)
        VersionMax = Version;
    }
  EcrireVersion(VersionMax);
}



AnsiString __fastcall CMigrations::ChaineVersion(unsigned int Version) const
{
  if (Version & 0x00000FF)
    return EICFormat("%u.%02u%c",(unsigned int)((Version & 0x0FF0000) >> 16),(unsigned int)((Version & 0x000FF00) >> 8),(int)(Version & 0x00000FF) + (unsigned int)'a' - 1);
  else
    return EICFormat("%u.%02u",(unsigned int)((Version & 0x0FF0000) >> 16),(unsigned int)((Version & 0x000FF00) >> 8));
}



void __fastcall CMigrationsDonnees::AjouterMigration(FonctionMigration Fonction,unsigned int Version,const AnsiString &Texte,bool MigrerSiVersionInferieure)
{
  #ifdef _DEBUG
    unsigned int VersionMax;
    if (Version != 0)
      for (int i = 0;i < m_ListeMigrations->Count;i++)
        {
          VersionMax = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
          if (Version < VersionMax)
            ASSERT(false);     //Il faut ajouter les migrations dans l'ordre croissant des versions.
        }
  #endif
  InfoMigration *Migration = new InfoMigration();
  Migration->Fonction = Fonction;
  Migration->Version = Version;
  Migration->Migrer = false;
  Migration->MigrerSiVersionInferieure = MigrerSiVersionInferieure;
  m_ListeMigrations->AddObject(Texte,(TObject *)Migration);
}



bool __fastcall CMigrationsDonnees::Migrer(const AnsiString &Titre)
{
  unsigned int Version,VersionPrecedente,VersionMax,IndiceDerniereMigrationALancer;
  bool Lancer,LancerSiAutreMigration = false;
  CBarre Barre;
  Barre.Titre = Titre;
  VersionMax = VersionPrecedente = IndiceDerniereMigrationALancer = 0;
  Version = LireVersion();
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    {
      if (((InfoMigration *)m_ListeMigrations->Objects[i])->Version > VersionMax)
        VersionMax = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
      Lancer = (Version < ((InfoMigration *)m_ListeMigrations->Objects[i])->Version) || (((InfoMigration *)m_ListeMigrations->Objects[i])->MigrerSiVersionInferieure && (Version > ((InfoMigration *)m_ListeMigrations->Objects[i])->Version));
      LancerSiAutreMigration = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version == 0;
      ((InfoMigration *)m_ListeMigrations->Objects[i])->Migrer = Lancer || LancerSiAutreMigration;
      if (Lancer || LancerSiAutreMigration)
        {
          if (!Barre.Messages.IsEmpty())
            Barre.Messages = Barre.Messages + "\n";
          Barre.Messages = Barre.Messages + m_ListeMigrations->Strings[i];
          IndiceDerniereMigrationALancer = i;
        }
    }
  if (Version == VersionMax)
    return false;                 //Aucune migration à faire
  Barre.Open();
  for (int i = 0;i < m_ListeMigrations->Count;i++)
    {
      try
        {
          if (((InfoMigration *)m_ListeMigrations->Objects[i])->Migrer)
            {
              ((InfoMigration *)m_ListeMigrations->Objects[i])->Fonction(&Barre);
              if (i != m_ListeMigrations->Count - 1 && IndiceDerniereMigrationALancer > (unsigned int)i)
                Barre.NextMessage();
              if (((InfoMigration *)m_ListeMigrations->Objects[i])->Version != 0)
                {
                  VersionPrecedente = Version;
                  Version = ((InfoMigration *)m_ListeMigrations->Objects[i])->Version;
                  EcrireVersion(Version);
                }
            }
        }
      catch (EUser &e)
        {
          if ((((InfoMigration *)m_ListeMigrations->Objects[i])->Version == 0) && (VersionPrecedente != 0)) //C'est une migration 0 qui a planté...
            EcrireVersion(VersionPrecedente);                                                               //...on remet l'ancienne version pour relancer la migration
          throw EUser(TAGL,AnsiString("Erreur durant la migration ") + ChaineVersion(((InfoMigration *)m_ListeMigrations->Objects[i])->Version) + "\n" +
                                      "(" + m_ListeMigrations->Strings[i] + ")\n"
                                      "\n" +
                                      e.Origine +
                                      "\n"
                                      "\n" +
                                      e.Message);
        }
      catch (Exception &e)
        {
          if ((((InfoMigration *)m_ListeMigrations->Objects[i])->Version == 0) && (VersionPrecedente != 0)) //C'est une migration 0 qui a planté...
            EcrireVersion(VersionPrecedente);                                                               //...on remet l'ancienne version pour relancer la migration
          throw EUser(TAGL,AnsiString("Erreur durant la migration ") + ChaineVersion(((InfoMigration *)m_ListeMigrations->Objects[i])->Version) + "\n" +
                                      "(" + m_ListeMigrations->Strings[i] + ")\n"
                                      "\n" +
                                      e.Message);
        }
    }
  return true;
}



/* EXEMPLE :

//Fonctions de migration
    void __fastcall Tdm::Suppression(CBarre *Barre)
    {
      ...
    }
    void __fastcall Tdm::Migration506(CBarre *Barre)
    {
      ...
    }
    void __fastcall Tdm::Migration507(CBarre *Barre)
    {
      ...
    }


//Fonction de mise à jour des tables
    void __fastcall Tdm::MAJAssistant()
    {//Les tables sont déjà crées
      CMigrations Migration("SI");
      Migration.AjouterMigration(Suppression, 0x0000000,"Suppression des tables temporaires");   //Fonction à lancer à chaque migration
      Migration.AjouterMigration(Migration506,0x0050600,"Migration en 5.06");
      Migration.AjouterMigration(Migration507,0x0050700,"Migration en 5.07");
      Migration.Migrer("Mise à jour de la 2072");
    }


//Fonction de création
    void __fastcall Tdm::CreationAssistant()
    {//C'est la première fois qu'on rentre dans l'assistant => aucune table n'est créée.
      ... // Création des tables (en version courante (5.07 dans l'exemple))
      CMigrations Migration("SI");
      Migration.Creer(0x0050700);    //Donner la version actuelle de l'assistant pour éviter les migrations précédentes à la version courante
    }


*/
